/** @file engine.c
 * The Jvm engine.
 *
 * Copyright (C) 2007 
 *   Renan Mendes Carvalho <aitherios@gmail.com>
 *   Pedro Marinho <pedro7x@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include"engine.h"
#include"engine_int.h"

/** Minimal allocation size for runtime areas*/
#define min_area 2097152

/** Alloc minimal space and setup the other pointers.
 * @param bp base pointer
 * @param sp stack pointer
 * @param tp top pointer
 */
#define ALLOC_RA(bp,sp,tp)						\
  bp = malloc (min_area);						\
  if (bp == NULL)							\
    {									\
      errormsg("start_jvm_thread", "error in memory allocation", 0 );	\
      return EXIT_FAILURE;						\
    }									\
  sp = bp;								\
  tp = bp; tp = (void *)((int *)tp + min_area);

/** Free a stack struct.
 * @param stack_struct
 */
#define FREE_RA(stack_struct) \
  free(stack_struct.bp);      \
  stack_struct.sp = NULL;     \
  stack_struct.tp = NULL;

/** 
 * Print one hex integer.
 * @param string preambule
 * @param var variable
 */ 
#define HPRINT(string, var)			\
  printf (string);				\
  printf ("\t\t%#.8x\n", var);

/** Global Static Jvm Runtime Area */
static th_runtime_area ra;

/**
 * Starts the java and thread specific runtime areas
 * @return
 */
int
start_jvm_thread ()
{
  ALLOC_RA(ra.method_area.bp, ra.method_area.sp, ra.method_area.tp);
  ALLOC_RA(ra.heap.bp, ra.heap.sp, ra.heap.tp);
  ALLOC_RA(ra.java.bp, ra.java.sp, ra.java.tp);
  ra.pc_reg.bp = malloc (16);
  ra.pc_reg.sp = ra.pc_reg.bp;
  ra.pc_reg.tp = (void *)((int *)ra.pc_reg.bp + 16);
  return EXIT_SUCCESS;
}

/**
 * Stop jvm and free allocated runtime areas
 * @return
 */
int
stop_jvm_thread ()
{
  FREE_RA(ra.method_area);
  FREE_RA(ra.heap);
  FREE_RA(ra.java);
  return EXIT_SUCCESS;
}

/**
 * Return a memory area in method area.
 * @param size memory size
 * @return memory area
 */
void *
push_method_area (size_t size)
{
  void * result;
  result = push_stack (&ra.method_area, size);
  if (result == NULL)
    {
      errormsg ("push_method_area", "memory allocation error", 0);
      exit(1);
    }
  return result;
}

/**
 * Return a memory area in the heap.
 * @param size memory size
 * @return memory area
 */
void *
push_heap (size_t size)
{
  void * result;
  result = push_stack (&ra.heap, size);
  if (result == NULL)
    {
      errormsg ("push_heap", "memory allocation error", 0);
      exit(1);
    }
  return result;
}

/**
 * Return a memory area from the java stack.
 * @param size memory size
 * @return memory area
 */
void *
push_java (size_t size)
{
  void * result;
  result = push_stack (&ra.java, size);
  if (result == NULL)
    {
      errormsg ("push_java", "memory allocation error", 0);
      exit(1);
    }
  return result;
}

/**
 * Unalloc a memory area from method area.
 * @param size memory size
 * @return the unalloced memory area
 */
void *
pop_method_area (size_t size)
{
  return pop_stack (&ra.method_area, size);
}

/**
 * Unalloc a memory area from heap.
 * @param size memory size
 * @return the unalloced memory area
 */
void *
pop_heap (size_t size)
{
  return pop_stack (&ra.heap, size);
}

/**
 * Unalloc a memory area from the java stack.
 * @param size memory size
 * @return the unalloced memory area
 */
void *
pop_java (size_t size)
{
  return pop_stack (&ra.java, size);
}

/**
 * Generic stack push memory area.
 * @param s stack
 * @param size memory size
 * @return the alloced memory area
 */
void *
push_stack (stack * s, size_t size)
{
  void * result;
  result = s->sp;
  /* OutOfMemoryError, StackOverflow */
  if ( (u1*)s->sp + size > (u1*)s->tp )
    {
      errormsg ("push_stack", "StackOverFlow", 0);
      return NULL;
    }
  /* else */
  s->sp = (void*)((u1*)s->sp + size);
  return result;
}

/**
 * Generic stack pop memory area.
 * @param s stack
 * @param size memory size
 * @return the alloced memory area
 */
void *
pop_stack (stack * s, size_t size)
{
  /* StackUnderFlow */
  if ( (u1*)s->sp - size < (u1*)s->bp )
    {
      errormsg ("pop_stack", "StackUnderFlow", 0);
      return s->sp;
    }
  /* else */
  s->sp = (void*)((u1*)s->sp - size);
  return s->sp;
}

/**
 * Get method area stack pointer.
 * @return stack pointer
 */
void * 
getsp_method_area ()
{
  return ra.method_area.sp;
}

/**
 * Get method area stack pointer.
 * @return stack pointer
 */
void * 
getsp_heap ()
{
  return ra.heap.sp;
}

/**
 * DEPRECADED. Get method area stack pointer.
 * @return stack pointer
 */
void * 
getsp_java ()
{
  return ra.java.sp;
}

/**
 * Print method code attribute.
 * @param ai
 */
void 
print_code_attribute (attribute_info * ai)
{
  u1 * cp; /**< code pointer */
  u4 tmp;
  u4 i;


  HPRINT("name index:", ai->attribute_name_index);
  HPRINT("length:", ai->attribute_length);
  cp = ai->info;
  /* stack depth */
  tmp = 0;
  /*  imeminv (&tmp, 4, cp, 2);*/
  memcpy (&tmp, cp, 2);
  tmp <<= 2 * 8;
  meminv (&tmp, 4);
  HPRINT("stack depth:", tmp);
  /* local variables */
  tmp = 0;
  memcpy (&tmp, cp + 2, 2);
  tmp <<= 2 * 8;
  meminv (&tmp, 4);
  HPRINT("local variables:", tmp);
  /* code length */
  tmp = 0;
  memcpy (&tmp, cp + 4, 4);
  meminv (&tmp, 4);
  HPRINT("code length:", tmp);
  /* opcodes */
  for (i = 8; i < ai->attribute_length; i++)
    {
      memcpy (&tmp, cp + i, 1);
      printf(opcode_string(tmp));
      puts("");
    }
}

/**
 * Invert memory bytes
 * @param p memory area
 * @param size memory area size
 */
void
meminv (void * p, size_t size)
{
  int i;
  u1 tmp;

  if (size % 2 == 0)
    i = size / 2;
  else
    i = (size - 1) / 2;
  for (;i != 0; i--)
    {
      memcpy (&tmp, (u1*)p + i - 1, 1);
      memcpy ((u1*)p + i - 1, (u1*)p + size - i, 1);
      memcpy ((u1*)p + size - i, &tmp, 1);
    }
}

/**
 * Return the minemonic string relative to a opcode.
 * @param op opcode integer
 * @return the opcode minemonic
 */
char *
opcode_string (u1 op)
{
  switch (op)
    {
    case 0x00: return "nop"; break; 
    case 0x01: return "aconst_null"; break; 
    case 0x02: return "iconst_m1"; break; 
    case 0x03: return "iconst_0"; break; 
    case 0x04: return "iconst_1"; break; 
    case 0x05: return "iconst_2"; break; 
    case 0x06: return "iconst_3"; break; 
    case 0x07: return "iconst_4"; break; 
    case 0x08: return "iconst_5"; break; 
    case 0x09: return "lconst_0"; break; 
    case 0x0a: return "lconst_1"; break; 
    case 0x0b: return "fconst_0"; break; 
    case 0x0c: return "fconst_1"; break; 
    case 0x0d: return "fconst_2"; break; 
    case 0x0e: return "dconst_0"; break; 
    case 0x0f: return "dconst_1"; break; 
    case 0x10: return "bipush"; break; 
    case 0x11: return "sipush"; break; 
    case 0x12: return "ldc"; break; 
    case 0x13: return "ldc_w"; break; 
    case 0x14: return "ldc2_w"; break; 
    case 0x15: return "iload"; break; 
    case 0x16: return "lload"; break; 
    case 0x17: return "fload"; break; 
    case 0x18: return "dload"; break; 
    case 0x19: return "aload"; break; 
    case 0x1a: return "iload_0"; break; 
    case 0x1b: return "iload_1"; break; 
    case 0x1c: return "iload_2"; break; 
    case 0x1d: return "iload_3"; break; 
    case 0x1e: return "lload_0"; break; 
    case 0x1f: return "lload_1"; break; 
    case 0x20: return "lload_2"; break; 
    case 0x21: return "lload_3"; break; 
    case 0x22: return "fload_0"; break; 
    case 0x23: return "fload_1"; break; 
    case 0x24: return "fload_2"; break; 
    case 0x25: return "fload_3"; break; 
    case 0x26: return "dload_0"; break; 
    case 0x27: return "dload_1"; break; 
    case 0x28: return "dload_2"; break; 
    case 0x29: return "dload_3"; break; 
    case 0x2a: return "aload_0"; break; 
    case 0x2b: return "aload_1"; break; 
    case 0x2c: return "aload_2"; break; 
    case 0x2d: return "aload_3"; break; 
    case 0x2e: return "iaload"; break; 
    case 0x2f: return "laload"; break; 
    case 0x30: return "faload"; break; 
    case 0x31: return "daload"; break; 
    case 0x32: return "aaload"; break; 
    case 0x33: return "baload"; break; 
    case 0x34: return "caload"; break; 
    case 0x35: return "saload"; break; 
    case 0x36: return "istore"; break; 
    case 0x37: return "lstore"; break; 
    case 0x38: return "fstore"; break; 
    case 0x39: return "dstore"; break; 
    case 0x3a: return "astore"; break; 
    case 0x3b: return "istore_0"; break; 
    case 0x3c: return "istore_1"; break; 
    case 0x3d: return "istore_2"; break; 
    case 0x3e: return "istore_3"; break; 
    case 0x3f: return "lstore_0"; break; 
    case 0x40: return "lstore_1"; break; 
    case 0x41: return "lstore_2"; break; 
    case 0x42: return "lstore_3"; break; 
    case 0x43: return "fstore_0"; break; 
    case 0x44: return "fstore_1"; break; 
    case 0x45: return "fstore_2"; break; 
    case 0x46: return "fstore_3"; break; 
    case 0x47: return "dstore_0"; break; 
    case 0x48: return "dstore_1"; break; 
    case 0x49: return "dstore_2"; break; 
    case 0x4a: return "dstore_3"; break; 
    case 0x4b: return "astore_0"; break; 
    case 0x4c: return "astore_1"; break; 
    case 0x4d: return "astore_2"; break; 
    case 0x4e: return "astore_3"; break; 
    case 0x4f: return "iastore"; break; 
    case 0x50: return "lastore"; break; 
    case 0x51: return "fastore"; break; 
    case 0x52: return "dastore"; break; 
    case 0x53: return "aastore"; break; 
    case 0x54: return "bastore"; break; 
    case 0x55: return "castore"; break; 
    case 0x56: return "sastore"; break; 
    case 0x57: return "pop"; break; 
    case 0x58: return "pop2"; break; 
    case 0x59: return "dup"; break; 
    case 0x5a: return "dup_x1"; break; 
    case 0x5b: return "dup_x2"; break; 
    case 0x5c: return "dup2"; break; 
    case 0x5d: return "dup2_x1"; break; 
    case 0x5e: return "dup2_x2"; break; 
    case 0x5f: return "swap"; break; 
    case 0x60: return "iadd"; break; 
    case 0x61: return "ladd"; break; 
    case 0x62: return "fadd"; break; 
    case 0x63: return "dadd"; break; 
    case 0x64: return "isub"; break; 
    case 0x65: return "lsub"; break; 
    case 0x66: return "fsub"; break; 
    case 0x67: return "dsub"; break; 
    case 0x68: return "imul"; break; 
    case 0x69: return "lmul"; break; 
    case 0x6a: return "fmul"; break; 
    case 0x6b: return "dmul"; break; 
    case 0x6c: return "idiv"; break; 
    case 0x6d: return "ldiv"; break; 
    case 0x6e: return "fdiv"; break; 
    case 0x6f: return "ddiv"; break; 
    case 0x70: return "irem"; break; 
    case 0x71: return "lrem"; break; 
    case 0x72: return "frem"; break; 
    case 0x73: return "drem"; break; 
    case 0x74: return "ineg"; break;
    case 0x75: return "lneg"; break; 
    case 0x76: return "fneg"; break; 
    case 0x77: return "dneg"; break; 
    case 0x78: return "ishl"; break; 
    case 0x79: return "lshl"; break; 
    case 0x7a: return "ishr"; break; 
    case 0x7b: return "lshr"; break; 
    case 0x7c: return "iushr"; break; 
    case 0x7d: return "lushr"; break; 
    case 0x7e: return "iand"; break; 
    case 0x7f: return "land"; break; 
    case 0x80: return "ior"; break; 
    case 0x81: return "lor"; break; 
    case 0x82: return "ixor"; break; 
    case 0x83: return "lxor"; break; 
    case 0x84: return "iinc"; break; 
    case 0x85: return "i2l"; break; 
    case 0x86: return "i2f"; break; 
    case 0x87: return "i2d"; break; 
    case 0x88: return "l2i"; break; 
    case 0x89: return "l2f"; break; 
    case 0x8a: return "l2d"; break; 
    case 0x8b: return "f2i"; break; 
    case 0x8c: return "f2l"; break; 
    case 0x8d: return "f2d"; break; 
    case 0x8e: return "d2i"; break; 
    case 0x8f: return "d2l"; break; 
    case 0x90: return "d2f"; break; 
    case 0x91: return "i2b"; break; 
    case 0x92: return "i2c"; break; 
    case 0x93: return "i2s"; break; 
    case 0x94: return "lcmp"; break; 
    case 0x95: return "fcmpl"; break; 
    case 0x96: return "fcmpg"; break; 
    case 0x97: return "dcmpl"; break; 
    case 0x98: return "dcmpg"; break; 
    case 0x99: return "ifeq"; break; 
    case 0x9a: return "ifne"; break; 
    case 0x9b: return "iflt"; break; 
    case 0x9c: return "ifge"; break; 
    case 0x9d: return "ifgt"; break; 
    case 0x9e: return "ifle"; break; 
    case 0x9f: return "if_icmpeq"; break; 
    case 0xa0: return "if_icmpne"; break; 
    case 0xa1: return "if_icmplt"; break; 
    case 0xa2: return "if_icmpge"; break; 
    case 0xa3: return "if_icmpgt"; break; 
    case 0xa4: return "if_icmple"; break; 
    case 0xa5: return "if_acmpeq"; break; 
    case 0xa6: return "if_acmpne"; break; 
    case 0xa7: return "goto"; break;  
    case 0xa8: return "jsr"; break; 
    case 0xa9: return "ret"; break; 
    case 0xaa: return "tableswitch"; break; 
    case 0xab: return "lookupswitch"; break; 
    case 0xac: return "ireturn"; break; 
    case 0xad: return "lreturn"; break; 
    case 0xae: return "freturn"; break; 
    case 0xaf: return "dreturn"; break; 
    case 0xb0: return "areturn"; break; 
    case 0xb1: return "return"; break; 
    case 0xb2: return "getstatic"; break; 
    case 0xb3: return "putstatic"; break; 
    case 0xb4: return "getfield"; break; 
    case 0xb5: return "putfield"; break; 
    case 0xb6: return "invokevirtual"; break; 
    case 0xb7: return "invokespecial"; break; 
    case 0xb8: return "invokestatic"; break; 
    case 0xb9: return "invokeinterface"; break; 
    case 0xba: return "xxxunusedxxx1"; break; 
    case 0xbb: return "new"; break; 
    case 0xbc: return "newarray"; break; 
    case 0xbd: return "anewarray"; break; 
    case 0xbe: return "arraylength"; break; 
    case 0xbf: return "athrow"; break; 
    case 0xc0: return "checkcast"; break; 
    case 0xc1: return "instanceof"; break; 
    case 0xc2: return "monitorenter"; break; 
    case 0xc3: return "monitorexit"; break; 
    case 0xc4: return "wide"; break; 
    case 0xc5: return "multianewarray"; break; 
    case 0xc6: return "ifnull"; break; 
    case 0xc7: return "ifnonnull"; break; 
    case 0xc8: return "goto_w"; break; 
    case 0xc9: return "jsr_w"; break; 
    case 0xca: return "breakpoint"; break; 
    case 0xfe: return "impdep1"; break; 
    case 0xff: return "impdep2"; break; 
    default: return "unknown";
    }
}

/**
 * Return the actual pc from static runtime areas.
 * @return pc
 */
void *
getpc ()
{
  return ra.pc;
}

/**
 * Set the pc from static runtime area.
 * @param p the value used to set
 */
void
setpc (void * p)
{
  ra.pc = p;
}

/**
 * Increment the pc.
 * @param i the increment
 */
void
incpc (int i)
{
  ra.pc = (void *)((u1 *)ra.pc + i);
}

/**
 * Return java base pointer.
 * @return java stack base poiter
 */
void *
getbp_java ()
{
  return ra.java.bp;
}
